package org.wevil.util;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;

/**
 * Http工具类
 */
public class HttpUtil {

    static {
        // 处理ssl请求问题
        disableSslVerification();
    }

    /**
     * get请求
     *
     * @param uri 请求地址，参数附加在地址后面
     */
    public static String doGet(String uri) {
        return doGet(uri, null, null);
    }

    /**
     * get请求
     *
     * @param uri            请求地址，参数附加在地址后面
     * @param returnEncoding 返回结果编码，不传的话，默认为UTF-8
     */
    public static String doGet(String uri, String returnEncoding, Header[] headers) {
        if (StringUtil.isBlank(returnEncoding)) {
            returnEncoding = "UTF-8";
        }
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(uri);
        if (headers != null) {
            httpGet.setHeaders(headers);
        }
        CloseableHttpResponse httpResponse = null;
        try {
            httpResponse = httpclient.execute(httpGet);
        } catch (IOException e) {
            e.printStackTrace();
        }
        HttpEntity entity = httpResponse.getEntity();
        // 获取返回的内容
        String content = null;
        try {
            content = convertStreamToString(entity.getContent(), returnEncoding);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return content;
    }

    /**
     * post请求
     *
     * @param uri 请求地址
     */
    public static String doPost(String uri) {
        return doPost(uri, null);
    }

    /**
     * post请求
     *
     * @param uri     请求地址
     * @param headers header里要添加的参数, key为名称，value为值
     */
    public static String doPost(String uri, Map<String, String> headers) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost(uri);
        // 设置Header
        if (headers != null && !headers.isEmpty()) {
            headers.forEach((key, value) -> {
                Header header = new BasicHeader(key, value);
                post.setHeader(header);
            });
        }
        //执行发送，获取相应结果
        try {
            CloseableHttpResponse response = httpclient.execute(post);
            HttpEntity httpEntity = response.getEntity();
            return EntityUtils.toString(httpEntity);
        } catch (IOException e) {
            return e.getMessage();
        }
    }

    /**
     * post请求，数据为json
     *
     * @param uri     请求地址
     * @param headers header里要添加的参数, key为名称，value为值
     * @param json    请求参数，json字符串
     * @return 返回请求结果
     */
    public static String doPostByJSON(String uri, Map<String, String> headers, String json) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost(uri);
        // 设置Header
        if (headers != null && !headers.isEmpty()) {
            headers.forEach((key, value) -> {
                Header header = new BasicHeader(key, value);
                post.setHeader(header);
            });
        }
        // 设置json
        StringEntity entity = new StringEntity(json, StandardCharsets.UTF_8);
        entity.setContentEncoding(StandardCharsets.UTF_8.toString());
        entity.setContentType("application/json");
        post.setEntity(entity);
        //执行发送，获取相应结果
        try {
            CloseableHttpResponse response = httpclient.execute(post);
            HttpEntity httpEntity = response.getEntity();
            return EntityUtils.toString(httpEntity);
        } catch (IOException e) {
            return e.getMessage();
        }
    }

    /**
     * post请求，数据为form
     *
     * @param uri     地址
     * @param params  form表单参数
     * @return 返回请求数据
     */
    public static String doPostByForm(String uri, Map<String, String> params) {
        return doPostByForm(uri, null, params);
    }

    /**
     * post请求，数据为form
     *
     * @param uri     地址
     * @param headers 需要添加的请求头，比如token
     * @param params  form表单参数
     * @return 返回请求数据
     * @since 2022/3/3
     */
    public static String doPostByForm(String uri, Map<String, String> headers, Map<String, String> params) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost post = new HttpPost(uri);
        // 设置Header
        if (headers != null && !headers.isEmpty()) {
            headers.forEach((key, value) -> {
                Header header = new BasicHeader(key, value);
                post.setHeader(header);
            });
        }
        // 设置Form表单数据
        List<NameValuePair> list = new ArrayList<>();
        params.forEach((key, value) -> {
            list.add(new BasicNameValuePair(key, value));
        });
        try {
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, StandardCharsets.UTF_8.toString());
            post.setEntity(entity);
        } catch (UnsupportedEncodingException e) {
            return e.getMessage();
        }
        //执行发送，获取相应结果
        try {
            CloseableHttpResponse response = httpclient.execute(post);
            HttpEntity httpEntity = response.getEntity();
            return EntityUtils.toString(httpEntity);
        } catch (IOException e) {
            return e.getMessage();
        }
    }

    /**
     * 带文件，带参数的POST请求
     *
     * @author wanglei
     * @since 2021/10/21
     */
    public static String doPostSubmitBody(String url, Map<String, String> map,
                                          String filePath, byte[] body_data, String charset) {
        // 设置三个常用字符串常量：换行、前缀、分界线（NEWLINE、PREFIX、BOUNDARY）；
        final String NEWLINE = "\r\n"; // 换行，或者说是回车
        final String PREFIX = "--"; // 固定的前缀
        final String BOUNDARY = "#"; // 分界线，就是上面提到的boundary，可以是任意字符串，建议写长一点，这里简单的写了一个#
        HttpsURLConnection httpsConn = null;
        BufferedInputStream bis = null;
        DataOutputStream dos = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            // 实例化URL对象。调用URL有参构造方法，参数是一个url地址；
            URL urlObj = new URL(url);
            // 调用URL对象的openConnection()方法，创建HttpURLConnection对象；
            httpsConn = (HttpsURLConnection) urlObj.openConnection();
            // 调用HttpURLConnection对象setDoOutput(true)、setDoInput(true)、setRequestMethod("POST")；
            httpsConn.setDoInput(true);
            httpsConn.setDoOutput(true);
            httpsConn.setRequestMethod("POST");
            // 设置Http请求头信息；（Accept、Connection、Accept-Encoding、Cache-Control、Content-Type、User-Agent），不重要的就不解释了，直接参考抓包的结果设置即可
            httpsConn.setUseCaches(false);
            httpsConn.setRequestProperty("Connection", "Keep-Alive");
            httpsConn.setRequestProperty("Accept", "*/*");
            httpsConn.setRequestProperty("Accept-Encoding", "gzip, deflate");
            httpsConn.setRequestProperty("Cache-Control", "no-cache");
            // 这个比较重要，按照上面分析的拼装出Content-Type头的内容
            httpsConn.setRequestProperty("Content-Type",
                    "multipart/form-data; boundary=" + BOUNDARY);
            httpsConn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            // 这个参数可以参考浏览器中抓出来的内容写，用chrome或者Fiddler抓吧看看就行
            httpsConn.setRequestProperty(
                    "User-Agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)");
            // 调用HttpURLConnection对象的connect()方法，建立与服务器的真实连接；
            httpsConn.connect();

            // 调用HttpURLConnection对象的getOutputStream()方法构建输出流对象；
            dos = new DataOutputStream(httpsConn.getOutputStream());
            // 获取表单中上传控件之外的控件数据，写入到输出流对象（根据上面分析的抓包的内容格式拼凑字符串）；
            if (map != null && !map.isEmpty()) { // 这时请求中的普通参数，键值对类型的，相当于上面分析的请求中的username，可能有多个
                for (Map.Entry<String, String> entry : map.entrySet()) {
                    String key = entry.getKey(); // 键，相当于上面分析的请求中的username
                    String value = map.get(key); // 值，相当于上面分析的请求中的sdafdsa
                    dos.writeBytes(PREFIX + BOUNDARY + NEWLINE); // 像请求体中写分割线，就是前缀+分界线+换行
                    dos.writeBytes("Content-Disposition: form-data; "
                            + "name=\"" + key + "\"" + NEWLINE); // 拼接参数名，格式就是Content-Disposition: form-data; name="key" 其中key就是当前循环的键值对的键，别忘了最后的换行
                    dos.writeBytes(NEWLINE); // 空行，一定不能少，键和值之间有一个固定的空行
                    //dos.writeBytes(URLEncoder.encode(value.toString(), charset)); // 将值写入
                    //dos.writeBytes(value.toString().getBytes()); // 将值写入
                    if (!"chunk".equals(key)) {
                        dos.write(value.toString().getBytes(StandardCharsets.UTF_8));
                    }
                    // 或者写成：dos.write(value.toString().getBytes(charset));
                    dos.writeBytes(NEWLINE); // 换行
                } // 所有循环完毕，就把所有的键值对都写入了
            }
            // 获取表单中上传附件的数据，写入到输出流对象（根据上面分析的抓包的内容格式拼凑字符串）；
            if (body_data != null && body_data.length > 0) {
                dos.writeBytes(PREFIX + BOUNDARY + NEWLINE);// 像请求体中写分割线，就是前缀+分界线+换行
                String fileName = filePath.substring(filePath
                        .lastIndexOf(File.separatorChar) + 1); // 通过文件路径截取出来文件的名称，也可以作文参数直接传过来
                // 格式是:Content-Disposition: form-data; name="请求参数名"; filename="文件名"
                // 我这里吧请求的参数名写成了uploadFile，是死的，实际应用要根据自己的情况修改
                // 不要忘了换行
                dos.writeBytes("Content-Disposition: form-data; " + "name=\""
                        + "uploadFile" + "\"" + "; filename=\"" + fileName
                        + "\"" + NEWLINE);
                // 换行，重要！！不要忘了
                dos.writeBytes(NEWLINE);
                dos.write(body_data); // 上传文件的内容
                dos.writeBytes(NEWLINE); // 最后换行
            }
            dos.writeBytes(PREFIX + BOUNDARY + PREFIX + NEWLINE); // 最后的分割线，与前面的有点不一样是前缀+分界线+前缀+换行，最后多了一个前缀
            dos.flush();
            // 调用HttpURLConnection对象的getInputStream()方法构建输入流对象；
            byte[] buffer = new byte[8 * 1024];
            int c = 0;
            // 调用HttpURLConnection对象的getResponseCode()获取客户端与服务器端的连接状态码。如果是200，则执行以下操作，否则返回null；
            if (httpsConn.getResponseCode() == 200) {
                bis = new BufferedInputStream(httpsConn.getInputStream());
                while ((c = bis.read(buffer)) != -1) {
                    baos.write(buffer, 0, c);
                    baos.flush();
                }
            }

            // 将输入流转成字节数组，返回给客户端。
            return baos.toString(charset);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (dos != null)
                    dos.close();
                if (bis != null)
                    bis.close();
                baos.close();
                assert httpsConn != null;
                httpsConn.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Authorization 为 Base Auth的post请求
     *
     * @param username  Basic Auth 用户名
     * @param password  Basic Auth 密码
     * @param paramsMap 请求体参数
     */
    public static String doPostByBasicAuth(String url,
                                           String username, String password,
                                           Map<String, String> paramsMap
    ) {
        // 创建HttpClient实例
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 创建POST请求
            HttpPost httpPost = new HttpPost(url);

            // 设置Basic Auth认证头
            String auth = username + ":" + password;
            byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
            String authHeader = "Basic " + new String(encodedAuth);
            httpPost.setHeader("Authorization", authHeader);

            // 设置请求体参数
            List<NameValuePair> params = new ArrayList<>();
            paramsMap.forEach((key, value) -> {
                params.add(new BasicNameValuePair(key, value));
            });
            httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));

            // 发送请求并获取响应
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                // 处理响应
                System.out.println("响应状态码: " + response.getStatusLine().getStatusCode());
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    String responseBody = EntityUtils.toString(entity);
                    System.out.println("响应内容: " + responseBody);
                    return responseBody;
                }
                // 确保实体内容被完全消费
                EntityUtils.consume(entity);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
        return "";
    }

    /**
     * Authorization 为 Base Auth的post请求
     *
     * @param token Bearer Token
     * @param jsonBody json字符串
     */
    public static String doPostByBearerToken(String url,
                                                   String token,
                                                   String jsonBody
    ) {
        // 创建HttpClient实例
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 创建POST请求
            HttpPost httpPost = new HttpPost(url);

            // 设置Bearer Token认证头
            String authHeader = "Bearer " + token;
            httpPost.setHeader("Authorization", authHeader);

            // 设置请求体参数
            StringEntity entityBody = new StringEntity(jsonBody, StandardCharsets.UTF_8);
            entityBody.setContentEncoding(StandardCharsets.UTF_8.toString());
            entityBody.setContentType("application/json");
            httpPost.setEntity(entityBody);

            // 发送请求并获取响应
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                // 处理响应
                System.out.println("响应状态码: " + response.getStatusLine().getStatusCode());
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    String responseBody = EntityUtils.toString(entity);
                    System.out.println("响应内容: " + responseBody);
                    return responseBody;
                }
                // 确保实体内容被完全消费
                EntityUtils.consume(entity);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        }
        return "";
    }

    /**
     * 将http协议获取的图片转储到本地
     *
     * @param url      http图片地址。示例：http://www.baidu.com/img/baidu_sylogo1.gif
     * @param filePath 本地存放地址。示例：示例：D:\\tmp\\files
     */
    public static void http2local(String url, String filePath) {
        byte[] btImg = getImageFromNetByUrl(url);
        if (null != btImg && btImg.length > 0) {
            // 获取文件名
            String[] temp = url.split("/");
            String imgName = temp[temp.length - 1];
            writeImageToDisk(btImg, filePath + File.separator + imgName);
        }
    }

    // 私有方法 ---------------------------------------------------------------------------//

    /**
     * InputStream 转 String
     *
     * @param is             输入流 InputStream
     * @param returnEncoding 返回数据编码
     */
    private static String convertStreamToString(InputStream is, String returnEncoding) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(is, returnEncoding));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    /**
     * 处理ssl请求问题
     *
     * @author wanglei
     * @since 2021/10/21
     */
    private static void disableSslVerification() {
        try {
            // Create a trust manager that does not validate certificate chains
            TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }

                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }
            }
            };

            // Install the all-trusting trust manager
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

            // Create all-trusting host name verifier
            HostnameVerifier allHostsValid = new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            };

            // Install the all-trusting host verifier
            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据地址获得数据的字节流
     *
     * @param strUrl 网络连接地址
     */
    private static byte[] getImageFromNetByUrl(String strUrl) {
        try {
            URL url = new URL(strUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5 * 1000);
            InputStream inStream = conn.getInputStream();//通过输入流获取图片数据
            return readInputStream(inStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 从输入流中获取数据
     *
     * @param inStream 输入流
     * @throws IOException 抛出文件流异常
     */
    private static byte[] readInputStream(InputStream inStream) throws IOException {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        inStream.close();
        return outStream.toByteArray();
    }

    /**
     * 将图片写入到磁盘
     *
     * @param img      图片数据流
     * @param filePath 文件绝对地址。示例：D:\\tmp\\files
     */
    private static void writeImageToDisk(byte[] img, String filePath) {
        try {
            File file = new File(filePath);
            FileOutputStream fops = new FileOutputStream(file);
            fops.write(img);
            fops.flush();
            fops.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
